#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <fcntl.h>
#include <string.h>

#ifdef NON_OS
#include "sys/alt_cache.h"
#else
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <unistd.h> 
#include <errno.h>
#endif

#include <config.h>
#include <systems.h>
#include <types.h>
#include <gapi.h>
#ifdef GPU_SUPPORT
#include <drivers/graphic/glt.h>
#include <drivers/graphic/gpu.h>
#endif

#ifndef NON_OS
sem_t gdev_lock;
pid_t gdev_lock_pid;
#endif

extern int fb;
extern int xres, yres, bpp, fbsize;
extern char *screen_base;

int default_draw_image(IMAGE *image)
{
    int i, w, h;
    int len, pixel_width;
    char *fb_start;
    
    if(!image)
        return -1;
    
    w = image->width;
    h = image->height;
    
    /* Adjust width and height if out of screen */
    if(image->x + image->width > xres)
        w = xres - image->x;
    if(image->y + image->height > yres)
        h = yres - image->y;
        
    len = w * bpp;
    fb_start = screen_base + ((xres * image->y + image->x) * bpp);
    pixel_width = image->width * bpp;
    
    for(i = 0; i < h; i++) {
        memcpy(fb_start, &image->data[pixel_width * i], len);
        fb_start += (xres * bpp);
    }
    
    GraphicFlush();
    
    return 0;
}

int default_draw_rect(RECT *rect)
{
    int i, w, h;
    int len;
    char *fb_start, *row_filled;
#if defined(CS_RGB565)
    u16 c = RGB2PIXEL565(rect->color->r, rect->color->g, rect->color->b);
#else
#warning "default_draw_rect: What should i do?";
#endif

    if(!rect)
        return -1;
    
    w = rect->width;
    h = rect->height;
    
    /* Adjust width and height if out of screen */
    if(rect->x + rect->width > xres)
        w = xres - rect->x;
    if(rect->y + rect->height > yres)
        h = yres - rect->y;
    
    len = w * bpp;
    fb_start = screen_base + ((xres * rect->y + rect->x) * bpp);
    
    if(rect->filled) {
        row_filled = malloc(len);
        for(i = 0; i < w; i++)
            ((typeof(c) *)row_filled)[i] = c;
            
        for(i = 0; i < h; i++) {
            memcpy(fb_start, row_filled, len);
            fb_start += (xres * bpp);
        }
        free(row_filled);
    }
    else {
        for(i = 0; i < w; i++)
            ((typeof(c) *)fb_start)[i] = c;
        
        if(h == rect->height) {
            fb_start += ((xres * (rect->height - 1))* bpp);
            for(i = 0; i < w; i++)
                ((typeof(c) *)fb_start)[i] = c;
            fb_start -= ((xres * (rect->height - 1)) * bpp);
        }
        
        for(i = 1; i < h; i++) {
            fb_start += (xres * (h - 1));
            ((typeof(c) *)fb_start)[i] = c;
            if(w == rect->width)
                ((typeof(c) *)(fb_start + len - bpp))[i] = c;
        }
    }
    
    GraphicFlush();
    
    return 0;
}

int DrawDot(DOT *dot)
{
    int ret = 0;
    
#ifdef GPU_SUPPORT
    glt_lock();
    ret = gpu_draw_dot(dot);
    glt_unlock();
#else
    RECT rect;
    if(!dot)
        return -1;
        
    rect.x = dot->x;
    rect.y = dot->y;
    rect.width = dot->size;
    rect.height = dot->size;
    rect.color = dot->color;
    rect.filled = true;
    rect.visible = dot->visible;
    rect.tp = dot->tp;
    
    ret = default_draw_rect(&rect);
#endif

    return ret;
}

int DrawLine(LINE *line)
{
    int ret = 0;
    
#ifdef GPU_SUPPORT
    glt_lock();
    ret = gpu_draw_line(line);
    glt_unlock();
#else
#warning "DrawLine(): What should I do ?"
#endif

  return ret;
}

int DrawRect(RECT *rect)
{
    int ret = 0;
    
#ifdef GPU_SUPPORT
    glt_lock();
    ret = gpu_draw_rect(rect);
    glt_unlock();
#else
    ret = default_draw_rect(rect);
#endif

  return ret;
}

int DrawImage(IMAGE *image)
{
    int ret = 0;
    IMAGE *opacify_image;
    u8 *tmp;
    
    if(image->opacity >= 100)
        return ret;
    
    if(image->opacity > 0) {
        if(!image->opacify_noncache) {
            /* Use cached opacified data */
            if(image->opacified_data) {
                tmp = image->data;
                image->data = image->opacified_data;
#ifdef GPU_SUPPORT
                glt_lock();
                ret = gpu_draw_image(image);
                glt_unlock();
#else
                ret = default_draw_image(image);
#endif                
                image->data = tmp;
                return ret;
            }
        }
        
        opacify_image = CaptureImage(NULL, image->x, image->y, image->width, image->height, true);
        if(!opacify_image) {
            WARNING("Capture failure\n");
            ret = -3;
            goto no_opacify;
        }
        
        if(OpacifyImage(image, opacify_image, 50) < 0) {
            free(opacify_image->data);
            free(opacify_image);
            ret = -3;
            goto no_opacify;
        }
        
        free(opacify_image->data);
        opacify_image->data = image->data;
        image->data = image->opacified_data;
#ifdef GPU_SUPPORT
        glt_lock();
        if(image->opacify_noncache)
            opacify_image->hw_dep_prop.end_layer = true;
        ret = gpu_draw_image(image);
        glt_unlock();
#else
        ret = default_draw_image(image);
#endif
        image->data = opacify_image->data;
        if(image->opacify_noncache) {
#ifdef GPU_SUPPORT
            /* We should wait until GPU finish drawing this image */
            gpu_wait_busy(true);
#endif
            free(image->opacified_data);
            image->opacified_data = NULL;
        }
        free(opacify_image);
        
        return ret;
    }

no_opacify:
#ifdef GPU_SUPPORT
    glt_lock();
    ret = gpu_draw_image(image);
    glt_unlock();
#else
    ret = default_draw_image(image);
#endif

  return ret;
}

int DrawText(TEXT *txt)
{
    int ret = 0;
    IMAGE *_char;
    int i, x, y, lines = 1;
    
    if(!txt)
        return -2;
    
    if(txt->txt_count == 0)
        return 0;
    
    _char = (IMAGE *)malloc(sizeof(IMAGE));
    if(!_char) {
        ERROR("Can't allocate memory for object\n");
        return -4;
    }
    memset(_char, 0x0, sizeof(IMAGE));
    _char->tp = txt->tp;
#ifdef GPU_SUPPORT
    _char->hw_dep_prop.command = txt->hw_dep_prop.command;
    _char->hw_dep_prop.angle = txt->hw_dep_prop.angle;
    _char->hw_dep_prop.scale = txt->hw_dep_prop.scale;
#endif
    x = txt->x;
    y = txt->y;
    for(i = 0; i < txt->txt_count; i++) {
        if(txt->data[i]) {
            if(x + txt->char_width[i] > txt->x + txt->line_width) {
                /* String is larger than line width setting */
                if(txt->multilines) {
                    if(++lines > txt->line_count)
                        break;
                    /* Re-calculate x and y of _char */
                    x = txt->x;
                    y += txt->txt_height;
                }
                else
                    break;
            }
            if(x > xres || y > yres) {
                //WARNING("Out of screen!\n");
                continue; /* Out of screen, ignore this character */
            }
            _char->x = x;
            _char->y = y;
            _char->opacity = txt->opacity;
            _char->opacify_noncache = txt->opacify_noncache;
            _char->width = txt->char_width[i];
            _char->height = txt->char_height[i];
            _char->data = txt->data[i];
            //DPRINTF("Drawing text #%d [W: %d, H: %d, D: %x]\n", i, _char->width, _char->height, _char->data);
            ret = DrawImage(_char);
            if(ret < 0)
                break;
            x += _char->width;
        }
    }
    free(_char);
    return ret;
}

void GraphicFlush()
{
#if defined(GPU_SUPPORT)
    gpu_flush(glt_get_current_index(), REG_COMMAND_WRBUFSW);
#elif defined(NON_OS)
    alt_dcache_flush_all();
#else
#warning "GraphicFlush(): What should I do ?"
#endif
}

void GraphicScaleImage(IMAGE *image, s32 multiplier)
{
#ifdef GPU_SUPPORT
    /* GPU does only support contraction */
    if(multiplier < 0)
        gpu_scale_image(image, 0 - multiplier);
#else
#warning "GraphicScaleImage(): What should I do ?"
#endif
}

void GraphicScaleLine(LINE *line, s32 multiplier)
{
#ifdef GPU_SUPPORT
    /* GPU does only support contraction */
    if(multiplier < 0)
        gpu_scale_line(line, 0 - multiplier);
#else
#warning "GraphicScaleLine(): What should I do ?"
#endif
}

void GraphicScaleRect(RECT *rect, s32 multiplier)
{
#ifdef GPU_SUPPORT
    /* GPU does only support contraction */
    if(multiplier < 0)
        gpu_scale_rect(rect, 0 - multiplier);
#else
#warning "GraphicScaleRect(): What should I do ?"
#endif
}

inline void GraphicDeviceLockInit()
{
#ifndef NON_OS
    sem_init(&gdev_lock, 0, 1);
    gdev_lock_pid = 0;
#endif
}

inline void GraphicDeviceLockDestroy()
{
#ifndef NON_OS
    sem_destroy(&gdev_lock);
#endif
}

inline void GraphicDeviceLock()
{
#ifndef NON_OS
    pid_t my_pid = getpid();

    sem_wait(&gdev_lock);
    gdev_lock_pid = my_pid;
#endif
}

inline void GraphicDeviceUnlock()
{
#ifndef NON_OS
    gdev_lock_pid = 0;
    sem_post(&gdev_lock);
#endif
}

/*
 * CaptureImage: Capture an image from src image.
 *          src: Source image to be captured.
 *            x: X-axis start point of captured image related to the src image. (relative position)
 *            y: Y-axis start point of captured image related to the src image. (relative position)
 *            w: Width of captured image.
 *            h: Height of captured image.
 *        which: Specify which page of framebuffer is to be captured. (Only used in GPU operation)
 * NOTE: If src is NULL, the image is captured from framebuffer using GPU.
 */
IMAGE *CaptureImage(IMAGE *src, u32 x, u32 y, u32 w, u32 h, BOOLEAN which)
{
    int i, j;
    IMAGE *captured;
    
    captured = (IMAGE *)malloc(sizeof(IMAGE));
    
    if(src) {
        /* Check region to be captured is out of image */
        if(x > src->width || y > src->height) {
            WARNING("Region to be captured is out of image\n");
            free(captured);
            return NULL;
        }
        
        memcpy(captured, src, sizeof(IMAGE));
        captured->x = src->x + x;
        captured->y = src->y + y;
        if(captured->x + w > src->x + src->width)
            captured->width = src->width - x;
        else
            captured->width = w;
        
        if(captured->y + h > src->y + src->height)
            captured->height = src->height - y;
        else
            captured->height = h;
        
        captured->data = malloc(captured->width * captured->height * bpp);

        if(!captured->data) {
            free(captured);
            return NULL;
        }
        
        for(i = 0; i < captured->height; i++) {
            for(j = 0; j < captured->width; j++) {
#if defined(CS_RGB565) || defined(CS_GRAY_10BIT)
                ((u16 *)(captured->data))[i * captured->width + j] = 
                          ((u16 *)(src->data))[(y + i) * src->width + (j + x)];
#elif defined(CS_RGB888)
                ((u32 *)(captured->data))[i * captured->width + j] = 
                          ((u32 *)(src->data))[(y + i) * src->width + (j + x)];
#elif defined(CS_GRAY_8BIT)
                ((u8 *)(captured->data))[i * captured->width + j] = 
                          ((u8 *)(src->data))[(y + i) * src->width + (j + x)];
#endif
            }
        }
        //DPRINTF("%s: \n"
        //        "x = %d, y = %d, w = %d, h = %d\n"
        //        "visible = %x, tp = %x\n"
        //        "command = %x, scale = %x, angle = %x\n",
        //        __func__, captured->x, captured->y, captured->width, captured->height,
        //        captured->visible, captured->tp,
        //        captured->hw_dep_prop.command, captured->hw_dep_prop.scale, captured->hw_dep_prop.angle);
    }
    else {
        memset(captured, 0, sizeof(IMAGE));
        captured->x = x;
        captured->y = y;
        if(captured->x + w > xres)
            captured->width = xres - x;
        else
            captured->width = w;
        
        if(captured->y + h > yres)
            captured->height = yres - y;
        else
            captured->height = h;

        captured->data = malloc(captured->width * captured->height * bpp);

#ifdef GPU_SUPPORT
        glt_lock();
        if(gpu_capture_image(captured, which) < 0) {
            glt_unlock();
            free(captured);
            return NULL;
        }
        glt_unlock();
#else
#warning "CaptureImage(): What should I do ?"
#endif /* GPU_SUPPORT */
    }
    if(captured->data)
        return captured;

    return 0;
}


int OpacifyImage(IMAGE *fg, IMAGE *bg, int opacity)
{
#if !defined(CS_GRAY_8BIT) && !defined(CS_GRAY_10BIT)
    int i, j;
    u8 *ResultData;
    COLOR fg_pixel;
    COLOR bg_pixel;
    int r, g, b;
    int width = bg->width;
    int height = bg->height;
    u16 temp;
#endif

    if(fg->width != bg->width || fg->height != bg->height)
        return -1;
        
    ResultData = (u8 *)malloc(width * height * bpp);

#if !defined(CS_GRAY_8BIT) && !defined(CS_GRAY_10BIT)
    if(!ResultData) {
        WARNING("Opacify process failed\n");
        return -2;
    }
    
    for(i = 0; i < height; i++) {
        for(j = 0; j < width; j++) {
            /* Get foreground pixel RGB */
            temp = ((u16 *)fg->data)[width * i + j];
            fg_pixel.r = (temp & 0xf800) >> 11;
            fg_pixel.g = (temp & 0x07e0) >> 5;
            fg_pixel.b = (temp & 0x001f);
            /* Get background pixel RGB */
            temp = ((u16 *)bg->data)[width * i + j];
            bg_pixel.r = (temp & 0xf800) >> 11;
            bg_pixel.g = (temp & 0x07e0) >> 5;
            bg_pixel.b = (temp & 0x001f);
            
            /* Ignore transparent color pixels if required */
            if(fg->tp &&
               fg_pixel.r == COLOR_TRANSPARENT.r && 
               fg_pixel.g == COLOR_TRANSPARENT.g && 
               fg_pixel.b == COLOR_TRANSPARENT.b) {
                temp = RGB2PIXEL565(fg_pixel.r, fg_pixel.g, fg_pixel.b);
            }
            else {
                /*
                r = (((fg_pixel.r + bg_pixel.r) * opacity)/100) & 0x01f;
                g = (((fg_pixel.g + bg_pixel.g) * opacity)/100) & 0x03f;
                b = (((fg_pixel.b + bg_pixel.b) * opacity)/100) & 0x01f;
                */
                r = ((fg_pixel.r + bg_pixel.r)/2) << 3;
                g = ((fg_pixel.g + bg_pixel.g)/2) << 2;
                b = ((fg_pixel.b + bg_pixel.b)/2) << 3;
                temp = RGB2PIXEL565(r, g, b);
            }
            ((u16 *)ResultData)[width * i + j] = temp;
        }
    }
    fg->opacified_data = ResultData;
#endif
    
    return 0;
}
